/**
 * Copyright (C)  Gadglet .
 *
 * This file is part of Gadglet
 *
 * Gadglet is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Gadglet is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Gadglet. If not, see <http://www.gnu.org/licenses/>.
 */

package com.gadglet.data.utils;

import java.util.HashMap;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import com.gadglet.core.GadgletRequestWrapper;
import com.gadglet.core.RequestException;
import com.gadglet.data.PersonalShareableContentItem;
import com.gadglet.data.SharedItemReff;
import com.gadglet.params.ReqErrorTypes;
import com.gadglet.params.ReqParamNames;
import com.gadglet.params.SharingType;

/**
 * A utility class to enable Google App Engine JDO DataStore methods
 * with Gadglet simple content item "PersonalShareableContentItem"
 * <p>
 * Note: 
 * Make sure that the property<br>
 * property name="datanucleus.appengine.storageVersion" value="WRITE_OWNED_CHILD_KEYS_TO_PARENTS"<br>
 * is defined in your jdoconfig.xml
 * 
 */
public class ContentItemHelper {

	private Logger log = Logger.getLogger(this.getClass().getName());

	/**
	 * @param item
	 * @param request
	 * @return true if the content item is shared with the curent user
	 */
	public boolean isSharedWithMe(PersonalShareableContentItem item,
			GadgletRequestWrapper request) {

		List<SharedItemReff> sharedUsers = item.getSharedItemReff();

		if (sharedUsers != null)
			for (SharedItemReff ref : sharedUsers) {
				if (ref.getSharedWithId().equals(request.getCurrentUserId()))
					return true;
			}

		return false;
	}

	/**
	 * @param item
	 * @param request
	 * @return true id the current user can update this item
	 */
	public boolean canUserModifyItem(PersonalShareableContentItem item,
			GadgletRequestWrapper request) {

		List<SharedItemReff> sharedUsers = item.getSharedItemReff();

		if (sharedUsers != null)
			for (SharedItemReff ref : sharedUsers) {
				if (ref.getSharedWithId().equals(request.getCurrentUserId())
						&& !ref.getSharingType().equals(SharingType.READ_ONLY))
					return true;
			}

		return false;
	}

	/**
	 * @param item
	 * @param request
	 * @return true if the current user is the owner of the item
	 */
	public boolean isItemOwner(PersonalShareableContentItem item,
			GadgletRequestWrapper request) {
		if (item == null || request == null)
			return false;
		else if (item.getOwnerId().equals(request.getCurrentUserId()))
			return true;
		else
			return false;

	}

	
	public void queryParameters(GadgletRequestWrapper request,
			java.util.Map<String, Object> params) {

		params.put("ownerIdParam", request.getCurrentUserId());

	}

	/**
	 * Delete an item from the DataStore
	 * @param request
	 * @param className
	 * @return
	 * @throws RequestException
	 */
	public boolean delete(GadgletRequestWrapper request, Class<?> className)
			throws RequestException {

		PersonalShareableContentItem item = null;
		PersistenceManager pm = null;

		try {
			pm = PMF.get().getPersistenceManager();
			item = getSingleItemByKeyForAction(pm, request, className);

			if (!isItemOwner(item, request))
				throw new RequestException(ReqErrorTypes.UNAUTHORIZED_OPERATION);

		} catch (RequestException e) {
			throw new RequestException(e.getErrorType());
		} catch (Exception e) {
			log.warning(e.getMessage());
			throw new RequestException(ReqErrorTypes.REQUEST_FAILED);
		}

		try {

			pm.deletePersistent(item);
		} catch (Exception e) {
			log.warning(e.getMessage());
			throw new RequestException(ReqErrorTypes.REQUEST_FAILED);

		} finally {
			pm.close();
		}
		return true;
	}

	
	
	/**
	 * Share item with a friend
	 * @param request
	 * @param className
	 * @throws RequestException
	 */
	public void shareItem(GadgletRequestWrapper request, Class<?> className)
			throws RequestException {

		PersistenceManager pm = PMF.get().getPersistenceManager();
		PersonalShareableContentItem item = (PersonalShareableContentItem) getSingleItemByKeyForAction(
				pm, request, className);

		String shareWith = request.getParameter(ReqParamNames.SHARE_WITH
				.getParamName());
		SharingType sharingType = request.getSharingType();
		SharedItemReff newShare = null;
		boolean shareExists = false;
		int remove = -1;
		List<SharedItemReff> sharings = null;

		// missing shareWith
		if (shareWith == null) {
			pm.close();
			throw new RequestException(ReqErrorTypes.MISSING_ARGUMENT);
		}

		// not owner
		if (!item.getOwnerId().equals(request.getCurrentUserId())) {
			pm.close();
			throw new RequestException(ReqErrorTypes.UNAUTHORIZED_OPERATION);

		}

		// can't change owner settings
		if (item.getOwnerId().equals(shareWith)) {
			pm.close();
			throw new RequestException(ReqErrorTypes.UNAUTHORIZED_OPERATION);

		}

		try {
			sharings = item.getSharedItemReff();
			int index = 0;
			for (SharedItemReff share : sharings) {

				if (share.getSharedWithId().equals(shareWith)
						&& !share.getSharingType().equals(SharingType.OWNER)) {
					shareExists = true;

					if (sharingType == null)
						remove = index;
					else
						share.setSharingType(sharingType);
				}

				index++;
			}
			// add
			if (remove > -1)
				item.getSharedItemReff().remove(remove);
			// add new sharing
			if (!shareExists && request.getSharingType() != null
					&& !request.getSharingType().equals(SharingType.OWNER)) {
				newShare = new SharedItemReff();

				newShare.setSharedWithId(request
						.getParameter(ReqParamNames.SHARE_WITH.getParamName()));
				newShare.setSharingType(request.getSharingType());
				item.addSharedItemReff(newShare);
			}

			updated(pm, item, request);
		} catch (RequestException e) {
			throw e;
		} catch (Exception e) {
			log.warning(e.getMessage());
			throw new RequestException(ReqErrorTypes.REQUEST_FAILED);
		}

	}

	/**
	 * Retrieve an item from the DataStore with a given PersistenceManager
	 * @param pm
	 * @param request
	 * @param className
	 * @return
	 * @throws RequestException
	 */
	public PersonalShareableContentItem getSingleItemByKeyForAction(
			PersistenceManager pm, GadgletRequestWrapper request,
			Class<?> className) throws RequestException {

		PersonalShareableContentItem item = null;

		String key = request.getItemKeyField();
		if (key == null)
			throw new RequestException(ReqErrorTypes.ITEM_NOT_FOUND);

		try {

			item = (PersonalShareableContentItem) pm.getObjectById(className,
					key);
			// access all fields before pm is closed to ensure fields are
			// available
			if (item != null && item.getSharedItemReff() != null)
				for (SharedItemReff ref : item.getSharedItemReff())
					ref.getSharingType();

		} catch (Exception e) {
			log.warning(e.getMessage());
			throw new RequestException(ReqErrorTypes.ITEM_NOT_FOUND);
		}

		if (item == null)
			throw new RequestException(ReqErrorTypes.ITEM_NOT_FOUND);

		return item;

	}

	/**
	 * Retrieve a single item from the DataStore 
	 * @param request
	 * @param className
	 * @return
	 * @throws RequestException
	 */
	public PersonalShareableContentItem getSingleItemByKey(
			GadgletRequestWrapper request, Class<?> className)
			throws RequestException {

		PersonalShareableContentItem item = null;

		String key = request.getItemKeyField();
		if (key == null)
			throw new RequestException(ReqErrorTypes.ITEM_NOT_FOUND);

		PersistenceManager pm = PMF.get().getPersistenceManager();

		try {

			item = (PersonalShareableContentItem) pm.getObjectById(className,
					key);

			// access all fields before pm is closed to ensure fields are
			// available
			if (item != null && item.getSharedItemReff() != null)
				for (SharedItemReff ref : item.getSharedItemReff())
					ref.getSharingType();

		} catch (Exception e) {
			log.warning(e.getMessage());
			throw new RequestException(ReqErrorTypes.ITEM_NOT_FOUND);
		} finally {
			pm.close();
		}

		if (item == null)
			throw new RequestException(ReqErrorTypes.ITEM_NOT_FOUND);

		return item;

	}

	/**
	 * Update an item in the DataStore
	 * @param pm
	 * @param item
	 * @param request
	 * @return
	 * @throws RequestException
	 */
	public boolean updated(PersistenceManager pm,
			PersonalShareableContentItem item, GadgletRequestWrapper request)
			throws RequestException {
		if (!canUserModifyItem(item, request)) {
			if (pm != null)
				pm.close();
			throw new RequestException(ReqErrorTypes.UNAUTHORIZED_OPERATION);

		}

		item.setUpdate();
		if (pm == null)
			pm = PMF.get().getPersistenceManager();
		pm.currentTransaction().begin();
		try {

			pm.makePersistent(item);
			pm.currentTransaction().commit();
		} catch (Exception e) {
			log.warning(e.getMessage());
			throw new RequestException(ReqErrorTypes.REQUEST_FAILED);
		} finally {
			if (pm.currentTransaction().isActive())
				pm.currentTransaction().rollback();
			pm.close();
		}

		return true;
	}

	/**
	 * Create a new item in the DataStore
	 * @param item
	 * @return
	 * @throws RequestException
	 */
	public String saveNew(PersonalShareableContentItem item)
			throws RequestException {
		SharedItemReff reff = new SharedItemReff(SharingType.OWNER,
				item.getOwnerId());

		item.addSharedItemReff(reff);

		PersonalShareableContentItem savedItem = null;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		pm.currentTransaction().begin();
		try {

			savedItem = pm.makePersistent(item);
			pm.currentTransaction().commit();
		} catch (Exception e) {
			log.warning(e.getMessage());
			throw new RequestException(ReqErrorTypes.REQUEST_FAILED);
		} finally {
			if (pm.currentTransaction().isActive())
				pm.currentTransaction().rollback();
			pm.close();
		}

		if (savedItem == null)
			return null;
		else
			return savedItem.getContentItemKey();

	}

	/**
	 * A list of items "owned" by the current user
	 * 
	 * @param request
	 * @param contentClass - items class name
	 * @param filterBy - filter By  expression: "fieldX==fieldXParam && fieldY==fieldYParam"
	 * @param filterByParamDeclaration - a list of parameters used in filterBy: "String fieldXParam, String fieldYParam"
	 * @param qParams - a list of parameters values used by filterBy
	 * @param orderBy - orderby expression :"fieldX ascending" 
	 * @param startFrom - first item index
	 * @param itemNum - number of item requested
	 * @param hasMore - object that will 
	 * @return A list of items "owned" by the current user
	 * @throws RequestException
	 */
	public List<?> getOwnerItems(GadgletRequestWrapper request,
			Class<?> contentClass, String filterBy,
			String filterByParamDeclaration,
			java.util.Map<String, Object> qParams, String orderBy,
			long startFrom, long itemNum, HasMoreItems hasMore)
			throws RequestException {

		List<?> items = null;
		Query query = null;
		PersistenceManager pm = null;
		java.util.Map<String, Object> params = null;

		if (qParams != null)
			params = qParams;
		else
			params = new HashMap<String, Object>();

		queryParameters(request, params);

		try {
			pm = PMF.get().getPersistenceManager();
			query = ownerItemsQuery(pm, contentClass, filterBy,
					filterByParamDeclaration, orderBy, startFrom, itemNum);

			items = (List<?>) query.executeWithMap(params);
			// the +1 is done do be able to know if has more results ==
			// true/false

			if (items.size() > itemNum)
				hasMore.setTrue();
			else
				hasMore.setFalse();
		} catch (Exception e) {
			log.warning(e.toString());
			throw new RequestException(ReqErrorTypes.REQUEST_FAILED);
		} finally {
			query.closeAll();
			pm.close();
		}
		return items;
	}

	/**
	 *  A Query for list of items "owned" by the current user
	 *  
	 * @param request
	 * @param contentClass - items class name
	 * @param filterBy - filter By  expression: "fieldX==fieldXParam && fieldY==fieldYParam"
	 * @param filterByParamDeclaration - a list of parameters used in filterBy: "String fieldXParam, String fieldYParam"
	 * @param qParams - a list of parameters values used by filterBy
	 * @param orderBy - orderby expression :"fieldX ascending" 
	 * @param startFrom - first item index
	 * @param itemNum - number of item requested
	 * @param hasMore - object that will 
	 * @return A Query for list of items "owned" by the current user
	 * @throws RequestException
	 */
	public Query ownerItemsQuery(PersistenceManager pm, Class<?> contentClass,
			String filerBy, String filterByParamDeclaration, String orderBy,
			long startFrom, long itemNum) {

		Query query = null;

		query = pm.newQuery(contentClass);
		StringBuffer filter = new StringBuffer();
		StringBuffer filterParameters = new StringBuffer();

		filterParameters.append("String ownerIdParam");

		filter.append("(ownerId==ownerIdParam)");

		if (filterByParamDeclaration != null)
			filterParameters.append("," + filterByParamDeclaration);

		if (filerBy != null)
			filter.append(" && " + filerBy);

		if (orderBy == null || orderBy.isEmpty())
			query.setOrdering("creation desc");
		else {
			query.setOrdering(orderBy);

		}

		query.declareParameters(filterParameters.toString());
		query.setFilter(filter.toString());
		// if number of items is not defined bring all
		if (itemNum > 0)
			query.setRange(startFrom, startFrom + itemNum + 1);

		return query;
	}

	
	
	/**
	 * A list of items "owned" by, or "shared" with the current user
	 * 
	 * @param request
	 * @param contentClass - items class name
	 * @param filterBy - filter By  expression: "fieldX==fieldXParam && fieldY==fieldYParam"
	 * @param filterByParamDeclaration - a list of parameters used in filterBy: "String fieldXParam, String fieldYParam"
	 * @param qParams - a list of parameters values used by filterBy
	 * @param orderBy - orderby expression :"fieldX ascending" 
	 * @param startFrom - first item index
	 * @param itemNum - number of item requested
	 * @param hasMore - object that will 
	 * @return A list of items "owned" by, or "shared" with the current user
	 * @throws RequestException
	 */
	public List<?> getSharedItems(GadgletRequestWrapper request,
			Class<?> contentClass, String filerBy,
			String filterByParamDecleration,
			java.util.Map<String, Object> qParams, String orderBy,
			long startFrom, long itemNum, HasMoreItems hasMore)
			throws RequestException {

		List<?> items = null;
		Query query = null;
		PersistenceManager pm = null;
		java.util.Map<String, Object> params = null;

		if (qParams != null)
			params = qParams;
		else
			params = new HashMap<String, Object>();

		queryParameters(request, params);

		try {
			pm = PMF.get().getPersistenceManager();
			query = sharedItemsQuery(pm, contentClass, filerBy,
					filterByParamDecleration, orderBy, startFrom, itemNum);

			items = (List<?>) query.executeWithMap(params);

			// load all reff items
			List<PersonalShareableContentItem> loop = (List<PersonalShareableContentItem>) items;
			for (int index = 0; index < loop.size(); index++) {

				List<SharedItemReff> shares = loop.get(index)
						.getSharedItemReff();

				if (shares != null)
					for (SharedItemReff reff : shares)
						;
			}

			if (items.size() > itemNum)
				hasMore.setTrue();
			else
				hasMore.setFalse();
		} catch (Exception e) {
		
			log.warning(e.getMessage());
			throw new RequestException(ReqErrorTypes.REQUEST_FAILED);
		} finally {
			query.closeAll();
			pm.close();
		}
		return items;
	}
	
	/**
	 *  A Query for a list of items "owned" by, or "shared" with the current user
	 *  
	 *  <p>
	 *  NOTE: 
	 *  Due to limitation in the JDO/DataStore implementation <br>
	 *  
	 *  1. Orderby is not supported <br>
	 *  2. FilterBy only supports "&&"
	 * 
	 *  </p>
	 *  
	 * @param request
	 * @param contentClass - items class name
	 * @param filterBy - filter By  expression: "fieldX==fieldXParam && fieldY==fieldYParam"
	 * @param filterByParamDeclaration - a list of parameters used in filterBy: "String fieldXParam, String fieldYParam"
	 * @param qParams - a list of parameters values used by filterBy
	 * @param orderBy - orderby expression :"fieldX ascending" 
	 * @param startFrom - first item index
	 * @param itemNum - number of item requested
	 * @param hasMore - object that will 
	 * @return A Query for a list of items "owned" by, or "shared" with the current user
	 * @throws RequestException
	 */

	public Query sharedItemsQuery(PersistenceManager pm, Class<?> contentClass,
			String filterBy, String filterByParamDecleration, String orderBy,
			long startFrom, long itemNum) {

		StringBuffer filter = new StringBuffer();
		StringBuffer filterParameters = new StringBuffer();

		if (filterBy != null)
			filter.append(" && " + filterBy );

		Query query = pm
				.newQuery("select from "
						+ contentClass.getName()
						+ " where "
						+ " sharedItemReff.contains(c) && c.sharedWithId == ownerIdParam"
						+	filter.toString());
		
		query.declareVariables(SharedItemReff.class.getName() + " c");

		filterParameters.append("String ownerIdParam");

		if (filterByParamDecleration != null)
			filterParameters.append("," + filterByParamDecleration);
		
		//TODO:Sorry, Not supported at this point by Google App Engine JDO
		//query.setOrdering(orderBy);

		query.declareParameters(filterParameters.toString());
		
		if (itemNum > 0)
			query.setRange(startFrom, startFrom + itemNum + 1);

		return query;

	}

}
